﻿#region Usings
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using CID.Domain;
using CID.Domain.Development;
using CID.Domain.Infrastructure;
using CID.Domain.Operations;
using CID.Domain.Users;
using CID.Tests.Support;
using CID.Web.Controllers;
using CID.Web.Models;
using CID.Web.Models.Development;
using CID.Web.Models.Infrastructure;
using CID.Web.Models.Operations;
using CID.Web.Models.Users;
using CID.Web.Support.TemplateContentEdition;
using NailsFramework.IoC;
using NailsFramework.Persistence;
using NailsFramework.UserInterface.TestingSupport;
using NUnit.Framework;
using Rhino.Mocks;
#endregion

namespace CID.Web.Tests.Controllers
{
    #region TestFixture configurations for each CRUDController implemented
    [TestFixture(typeof(IUser), typeof(User), typeof(UserViewModel), typeof(UserController))]
    [TestFixture(typeof(IPackageType), typeof(PackageType), typeof(PackageTypeViewModel), typeof(PackageTypeController))]
    [TestFixture(typeof(ITemplateType), typeof(TemplateType), typeof(TemplateTypeViewModel), typeof(TemplateTypeController))]
    [TestFixture(typeof(IDeploymentAgent), typeof(DeploymentAgent), typeof(DeploymentAgentViewModel), typeof(DeploymentAgentController))]
    [TestFixture(typeof(ILocation), typeof(Location), typeof(LocationViewModel), typeof(LocationController))]
    [TestFixture(typeof(IComponent), typeof(Component), typeof(ComponentViewModel), typeof(ComponentController))]
    [TestFixture(typeof(IRelease), typeof(Release), typeof(ReleaseViewModel), typeof(ReleaseController))]
    [TestFixture(typeof(ITemplate), typeof(Template), typeof(TemplateViewModel), typeof(TemplateController))]
    [TestFixture(typeof(IEnvironment), typeof(Environment), typeof(EnvironmentViewModel), typeof(EnvironmentController))]
    [TestFixture(typeof(IServerRole), typeof(ServerRole), typeof(ServerRoleViewModel), typeof(ServerRoleController))]
    [TestFixture(typeof(IServer), typeof(Server), typeof(ServerViewModel), typeof(ServerController))]
    #endregion
    class CRUDControllerTests<TModelInterface, TModel, TViewModel, TController> : BaseWebTests
        where TModelInterface : class, IEntity
        where TModel : class, TModelInterface
        where TViewModel : BaseViewModel<TModelInterface>, new()
        where TController : BaseCRUDController<TModelInterface, TModel, TViewModel>, new()
    {
        private TController controller;

        [Inject]
        public IBag<TModel> Entities { get; set; }

        public override void SetUp()
        {
            base.SetUp();
            controller = TestControllers.Get<TController>();
            var requestContext = StubsBuilder.BuildRequestContextStub();
            controller.ControllerContext = new ControllerContext(requestContext, controller);
            controller.Url = new UrlHelper(requestContext);
            TemplateContentEditorRegistry.Instance.TemplateContentEditorViewsBasePath = ".";
        }

        private TViewModel StubViewModel(TModel model)
        {
            return ViewModelStubBuilder.BuildViewModelStub<TViewModel, TModelInterface>(model);
        }

        [Test]
        public void Index_ShouldCreateDefaultViewWithListOfEntities()
        {
            var result = controller.Index();

            Assert.IsNotNull(result);
            Assert.AreEqual("", result.ViewName);
            Assert.IsInstanceOf<IEnumerable<TModel>>(result.Model);
            Assert.AreEqual(Entities, result.Model);
        }

        [Test]
        public void Details_ShouldCreateDefaultViewWithEntity()
        {
            var model = Entities.First();
            var viewModel = StubViewModel(model);

            var result = controller.Details(model.Id);

            Assert.IsNotNull(result);
            Assert.AreEqual("", result.ViewName);
            Assert.IsInstanceOf<TViewModel>(result.Model);
            Assert.AreEqual(model.Id, ((TViewModel)result.Model).Id);
        }
        
        #region edit
        [Test]
        public void GetEdit_ShouldCreateDefaultViewWithSelectedEntity()
        {
            var model = Entities.First();
            var viewModel = StubViewModel(model);

            var result = controller.Edit(model.Id);

            Assert.IsNotNull(result);
            Assert.AreEqual("", result.ViewName);
            Assert.IsInstanceOf<TViewModel>(result.Model);
            Assert.AreEqual(model.Id, ((TViewModel)result.Model).Id);
        }

        [Test]
        public void PostEdit_ValidData_ShouldUpdateEntityAndRedirectToIndex()
        {
            var model = Entities.First();
            var viewModel = StubViewModel(model);

            var result = controller.Edit(viewModel.Id, viewModel);

            Assert.IsNotNull(result);
            Assert.IsInstanceOf<RedirectToRouteResult>(result);
            Assert.AreEqual("Index", ((RedirectToRouteResult)result).RouteValues["action"]);
            viewModel.AssertWasCalled(x => x.UpdateModel(model));
        }

        [Test]
        public void PostEdit_InvalidData_ShouldReturnEditView()
        {
            var model = Entities.First();
            var viewModel = StubViewModel(model);
            controller.ModelState.AddModelError("", "");

            var result = controller.Edit(viewModel.Id, viewModel);

            Assert.IsNotNull(result);
            Assert.IsInstanceOf<ViewResult>(result);
            Assert.AreEqual("", ((ViewResult)result).ViewName);
            Assert.IsInstanceOf<TViewModel>(((ViewResult)result).Model);
            Assert.AreEqual(viewModel, ((ViewResult)result).Model);
            viewModel.AssertWasNotCalled(x => x.UpdateModel(model));
        }

        [Test]
        public void PostEdit_DomainExceptionThrown_ShouldReturnEditView()
        {
            var model = Entities.First();
            var viewModel = StubViewModel(model);
            viewModel.Stub(x => x.UpdateModel(model)).Throw(new DomainExceptionStub());

            var result = controller.Edit(viewModel.Id, viewModel);

            Assert.IsNotNull(result);
            Assert.IsInstanceOf<ViewResult>(result);
            Assert.AreEqual("", ((ViewResult)result).ViewName);
            Assert.IsInstanceOf<TViewModel>(((ViewResult)result).Model);
            Assert.AreEqual(viewModel, ((ViewResult)result).Model);
            viewModel.AssertWasCalled(x => x.UpdateModel(model));
        }
        #endregion

        #region Create
        [Test]
        public void GetCreate_ShouldCreateDefaultViewWithNewEntity()
        {
            var result = controller.Create();

            Assert.IsNotNull(result);
            Assert.AreEqual("", result.ViewName);
            Assert.IsInstanceOf<TViewModel>(result.Model);
            Assert.AreEqual(0, ((TViewModel)result.Model).Id);
        }

        [Test]
        public void PostCreate_ValidData_ShouldCreateEntityAndRedirectToIndex()
        {
            var originalEntitiesCount = Entities.Count();
            var viewModel = ViewModelStubBuilder.BuildViewModel<TViewModel, TModelInterface>();

            var result = controller.Create(viewModel);

            Assert.IsNotNull(result);
            Assert.IsInstanceOf<RedirectToRouteResult>(result);
            Assert.AreEqual("Index", ((RedirectToRouteResult)result).RouteValues["action"]);
            Assert.AreEqual(originalEntitiesCount + 1, Entities.Count());
            var model = Entities.Skip(originalEntitiesCount).FirstOrDefault();
            viewModel.AssertWasCalled(x => x.UpdateModel(model));
        }

        [Test]
        public void PostCreate_InvalidData_ShouldReturnCreateView()
        {
            var originalEntitiesCount = Entities.Count();
            var viewModel = ViewModelStubBuilder.BuildViewModel<TViewModel, TModelInterface>();
            controller.ModelState.AddModelError("", "");

            var result = controller.Create(viewModel);

            Assert.IsNotNull(result);
            Assert.IsInstanceOf<ViewResult>(result);
            Assert.AreEqual("", ((ViewResult)result).ViewName);
            Assert.IsInstanceOf<TViewModel>(((ViewResult)result).Model);
            Assert.AreEqual(viewModel, ((ViewResult)result).Model);
            Assert.AreEqual(originalEntitiesCount, Entities.Count());
            viewModel.AssertWasNotCalled(x => x.UpdateModel(((TModel)null)), x => x.IgnoreArguments());
        }

        [Test]
        public void PostCreate_DomainExceptionThrown_ShouldReturnCreateView()
        {
            var originalEntitiesCount = Entities.Count();
            var viewModel = ViewModelStubBuilder.BuildViewModel<TViewModel, TModelInterface>();
            viewModel.Stub(x => x.UpdateModel(null)).IgnoreArguments().Throw(new DomainExceptionStub());

            var result = controller.Create(viewModel);

            Assert.IsNotNull(result);
            Assert.IsInstanceOf<ViewResult>(result);
            Assert.AreEqual("", ((ViewResult)result).ViewName);
            Assert.IsInstanceOf<TViewModel>(((ViewResult)result).Model);
            Assert.AreEqual(viewModel, ((ViewResult)result).Model);
            Assert.AreEqual(originalEntitiesCount, Entities.Count());
            viewModel.AssertWasCalled(x => x.UpdateModel(((TModel)null)), x => x.IgnoreArguments());
        }
        #endregion
    }
}
